/*
 *  Copyright (c) 2013 Hai Bison
 *
 *  See the file LICENSE at the root directory of this project for copying
 *  permission.
 */

package com.haibison.android.hutieu;

import java.util.ArrayList;
import java.util.regex.Pattern;

import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.SystemClock;
import android.provider.OpenableColumns;
import android.text.TextUtils;
import android.text.format.DateUtils;

import com.haibison.android.hutieu.utils.IntentCompat_v16;
import com.haibison.android.hutieu.utils.MimeTypes;
import com.haibison.android.hutieu.utils.SimpleWeakEncryption;

/**
 * Utilities.
 * 
 * @author Hai Bison
 * 
 */
public class ShareProviderUtils {

    /**
     * Maximum delta time for passport.
     */
    public static final long MAX_DELTA_TIME_FOR_PASSPORT = DateUtils.MINUTE_IN_MILLIS;

    /**
     * This is singleton class.
     */
    private ShareProviderUtils() {
    }// ShareProviderUtils()

    /**
     * Creates new intent with {@link Intent#ACTION_SEND} to send a resource.
     * 
     * @param context
     *            the context.
     * @param resId
     *            the resource ID.
     * @param compressed
     *            specifies whether this resource is compressed or not.
     * @return the intent.
     */
    public static Intent createSendIntent(Context context, int resId,
            boolean compressed) {
        return createSendIntent(context, resId, compressed, null);
    }// createSendIntent()

    /**
     * Creates new intent with {@link Intent#ACTION_SEND} to send a resource.
     * 
     * @param context
     *            the context.
     * @param resId
     *            the resource ID.
     * @param compressed
     *            specifies whether this resource is compressed or not.
     * @param name
     *            the resource name, can be {@code null}.
     * @return the intent.
     */
    public static Intent createSendIntent(Context context, int resId,
            boolean compressed, String name) {
        return createSendIntent(context, resId, compressed, name, null);
    }// createSendIntent()

    /**
     * Creates new intent with {@link Intent#ACTION_SEND} to send a resource.
     * 
     * @param context
     *            the context.
     * @param resId
     *            the resource ID.
     * @param compressed
     *            specifies whether this resource is compressed or not.
     * @param name
     *            the resource name, can be {@code null}.
     * @param mimeType
     *            the resource MIME type, can be {@code null}. If {@code name}
     *            is not {@code null} and {@code mimeType} is {@code null}, then
     *            this method automatically determines the MIME type from
     *            {@code name} <b>via</b> {@link MimeTypes}.
     * @return the intent.
     */
    public static Intent createSendIntent(Context context, int resId,
            boolean compressed, String name, String mimeType) {
        Uri uri;
        if (TextUtils.isEmpty(name))
            uri = ShareContract.genContentIdUri(context, resId, compressed,
                    mimeType);
        else
            uri = ShareContract.genContentNameUri(context, resId, compressed,
                    name, mimeType);

        if (!TextUtils.isEmpty(name) && TextUtils.isEmpty(mimeType))
            mimeType = MimeTypes.getUri(name);

        Intent result = new Intent(Intent.ACTION_SEND);
        result.putExtra(Intent.EXTRA_STREAM, uri);
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN)
            result.setType(mimeType);
        else
            IntentCompat_v16.setTypeAndNormalize(result, mimeType);

        return result;
    }// createSendIntent()

    /**
     * Sends a resource.
     * <p/>
     * <b>Note</b> that this method requires to be called inside an activity or
     * a fragment. If you want to call this somewhere else, use
     * {@code createSendIntent()} methods with
     * {@link Intent#FLAG_ACTIVITY_NEW_TASK}.
     * 
     * @param context
     *            the context.
     * @param resId
     *            the resource ID.
     * @param compressed
     *            specifies whether this resource is compressed or not.
     * @throws ActivityNotFoundException
     *             if there is no activity to handle {@link Intent#ACTION_SEND}.
     */
    public static void send(Context context, int resId, boolean compressed) {
        send(context, resId, compressed, null);
    }// createSendIntent()

    /**
     * Sends a resource.
     * <p/>
     * <b>Note</b> that this method requires to be called inside an activity or
     * a fragment. If you want to call this somewhere else, use
     * {@code createSendIntent()} methods with
     * {@link Intent#FLAG_ACTIVITY_NEW_TASK}.
     * 
     * @param context
     *            the context.
     * @param resId
     *            the resource ID.
     * @param compressed
     *            specifies whether this resource is compressed or not.
     * @param name
     *            the resource name, can be {@code null}.
     * @throws ActivityNotFoundException
     *             if there is no activity to handle {@link Intent#ACTION_SEND}.
     */
    public static void send(Context context, int resId, boolean compressed,
            String name) {
        send(context, resId, compressed, name, null);
    }// send()

    /**
     * Sends a resource.
     * <p/>
     * <b>Note</b> that this method requires to be called inside an activity or
     * a fragment. If you want to call this somewhere else, use
     * {@code createSendIntent()} methods with
     * {@link Intent#FLAG_ACTIVITY_NEW_TASK}.
     * 
     * @param context
     *            the context.
     * @param resId
     *            the resource ID.
     * @param compressed
     *            specifies whether this resource is compressed or not.
     * @param name
     *            the resource name, can be {@code null}.
     * @param mimeType
     *            the resource MIME type, can be {@code null}. If {@code name}
     *            is not {@code null} and {@code mimeType} is {@code null}, then
     *            this method automatically determines the MIME type from
     *            {@code name} <b>via</b> {@link MimeTypes}.
     * @throws ActivityNotFoundException
     *             if there is no activity to handle {@link Intent#ACTION_SEND}.
     */
    public static void send(Context context, int resId, boolean compressed,
            String name, String mimeType) {
        context.startActivity(createSendIntent(context, resId, compressed,
                name, mimeType));
    }// send()

    /**
     * Creates new intent with {@link Intent#ACTION_SEND} to send an asset
     * resource.
     * 
     * @param context
     *            the context.
     * @param assetPath
     *            the path to an asset resource.
     * @param compressed
     *            specifies whether this resource is compressed or not.
     * @return the intent.
     */
    public static Intent createSendIntent(Context context, String assetPath,
            boolean compressed) {
        return createSendIntent(context, assetPath, compressed, null);
    }// createSendIntent()

    /**
     * Creates new intent with {@link Intent#ACTION_SEND} to send an asset
     * resource.
     * 
     * @param context
     *            the context.
     * @param assetPath
     *            the path to an asset resource.
     * @param compressed
     *            specifies whether this resource is compressed or not.
     * @param name
     *            the resource name, can be {@code null}.
     * @return the intent.
     */
    public static Intent createSendIntent(Context context, String assetPath,
            boolean compressed, String name) {
        return createSendIntent(context, assetPath, compressed, name, null);
    }// createSendIntent()

    /**
     * Creates new intent with {@link Intent#ACTION_SEND} to send an asset
     * resource.
     * 
     * @param context
     *            the context.
     * @param assetPath
     *            the path to an asset resource.
     * @param compressed
     *            specifies whether this resource is compressed or not.
     * @param name
     *            the resource name, can be {@code null}.
     * @param mimeType
     *            the resource MIME type, can be {@code null}. If {@code name}
     *            is not {@code null} and {@code mimeType} is {@code null}, then
     *            this method automatically determines the MIME type from
     *            {@code name} <b>via</b> {@link MimeTypes}.
     * @return the intent.
     */
    public static Intent createSendIntent(Context context, String assetPath,
            boolean compressed, String name, String mimeType) {
        Uri uri;
        if (TextUtils.isEmpty(name))
            uri = ShareContract.genContentAssetUri(context, assetPath,
                    compressed, mimeType);
        else
            uri = ShareContract.genContentNameUri(context, assetPath,
                    compressed, name, mimeType);

        if (!TextUtils.isEmpty(name) && TextUtils.isEmpty(mimeType))
            mimeType = MimeTypes.getUri(name);

        Intent result = new Intent(Intent.ACTION_SEND);
        result.putExtra(Intent.EXTRA_STREAM, uri);
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN)
            result.setType(mimeType);
        else
            IntentCompat_v16.setTypeAndNormalize(result, mimeType);

        return result;
    }// createSendIntent()

    /**
     * Sends an asset resource.
     * <p/>
     * <b>Note</b> that this method requires to be called inside an activity or
     * a fragment. If you want to call this somewhere else, use
     * {@code createSendIntent()} methods with
     * {@link Intent#FLAG_ACTIVITY_NEW_TASK}.
     * 
     * @param context
     *            the context.
     * @param assetPath
     *            the path to an asset resource.
     * @param compressed
     *            specifies whether this resource is compressed or not.
     * @throws ActivityNotFoundException
     *             if there is no activity to handle {@link Intent#ACTION_SEND}.
     */
    public static void send(Context context, String assetPath,
            boolean compressed) {
        send(context, assetPath, compressed, null);
    }// send()

    /**
     * Sends an asset resource.
     * <p/>
     * <b>Note</b> that this method requires to be called inside an activity or
     * a fragment. If you want to call this somewhere else, use
     * {@code createSendIntent()} methods with
     * {@link Intent#FLAG_ACTIVITY_NEW_TASK}.
     * 
     * @param context
     *            the context.
     * @param assetPath
     *            the path to an asset resource.
     * @param compressed
     *            specifies whether this resource is compressed or not.
     * @param name
     *            the resource name, can be {@code null}.
     * @throws ActivityNotFoundException
     *             if there is no activity to handle {@link Intent#ACTION_SEND}.
     */
    public static void send(Context context, String assetPath,
            boolean compressed, String name) {
        send(context, assetPath, compressed, name, null);
    }// send()

    /**
     * Sends an asset resource.
     * <p/>
     * <b>Note</b> that this method requires to be called inside an activity or
     * a fragment. If you want to call this somewhere else, use
     * {@code createSendIntent()} methods with
     * {@link Intent#FLAG_ACTIVITY_NEW_TASK}.
     * 
     * @param context
     *            the context.
     * @param assetPath
     *            the path to an asset resource.
     * @param compressed
     *            specifies whether this resource is compressed or not.
     * @param name
     *            the resource name, can be {@code null}.
     * @param mimeType
     *            the resource MIME type, can be {@code null}. If {@code name}
     *            is not {@code null} and {@code mimeType} is {@code null}, then
     *            this method automatically determines the MIME type from
     *            {@code name} <b>via</b> {@link MimeTypes}.
     * @throws ActivityNotFoundException
     *             if there is no activity to handle {@link Intent#ACTION_SEND}.
     */
    public static void send(Context context, String assetPath,
            boolean compressed, String name, String mimeType) {
        context.startActivity(createSendIntent(context, assetPath, compressed,
                name, mimeType));
    }// send()

    /**
     * Creates intent to send multiple URIs.
     * 
     * @param context
     *            the context.
     * @param mime
     *            the MIME type, if {@code null}, {@link #MIME_TYPE_UNKNOWN}
     *            will be used.
     * @param uris
     *            the URIs.
     * @return the intent.
     */
    public static Intent createSendMultipleIntent(Context context, String mime,
            Uri... uris) {
        ArrayList<Uri> _ = new ArrayList<Uri>();
        for (Uri uri : uris)
            _.add(uri);
        return createSendMultipleIntent(context, mime, _);
    }// createSendMultipleIntent()

    /**
     * Creates intent to send multiple URIs.
     * 
     * @param context
     *            the context.
     * @param mime
     *            the MIME type, if {@code null}, {@link #MIME_TYPE_UNKNOWN}
     *            will be used.
     * @param uris
     *            the URIs.
     * @return the intent.
     */
    public static Intent createSendMultipleIntent(Context context, String mime,
            ArrayList<Uri> uris) {
        if (TextUtils.isEmpty(mime))
            mime = MimeTypes.MIME_TYPE_UNKNOWN;

        Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN)
            intent.setType(mime);
        else
            IntentCompat_v16.setTypeAndNormalize(intent, mime);
        intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);

        return intent;
    }// createSendMultipleIntent()

    /**
     * Sends an array of URIs.
     * <p/>
     * <b>Note</b> that this method requires to be called inside an activity or
     * a fragment. If you want to call this somewhere else, use
     * {@code createSendMultipleIntent()} methods with
     * {@link Intent#FLAG_ACTIVITY_NEW_TASK}.
     * 
     * @param context
     *            the context.
     * @param mime
     *            the MIME type, if {@code null}, {@link #MIME_TYPE_UNKNOWN}
     *            will be used.
     * @param uris
     *            the URIs to send.
     * @throws ActivityNotFoundException
     *             if there is no activity to handle
     *             {@link Intent#ACTION_SEND_MULTIPLE}.
     */
    public static void sendMultiple(Context context, String mime, Uri... uris) {
        context.startActivity(createSendMultipleIntent(context, mime, uris));
    }// sendMultiple()

    /**
     * Sends a list of URIs.
     * <p/>
     * <b>Note</b> that this method requires to be called inside an activity or
     * a fragment. If you want to call this somewhere else, use
     * {@code createSendMultipleIntent()} methods with
     * {@link Intent#FLAG_ACTIVITY_NEW_TASK}.
     * 
     * @param context
     *            the context.
     * @param mime
     *            the MIME type, if {@code null}, {@link #MIME_TYPE_UNKNOWN}
     *            will be used.
     * @param uris
     *            the URIs to send.
     * @throws ActivityNotFoundException
     *             if there is no activity to handle
     *             {@link Intent#ACTION_SEND_MULTIPLE}.
     */
    public static void sendMultiple(Context context, String mime,
            ArrayList<Uri> uris) {
        context.startActivity(createSendMultipleIntent(context, mime, uris));
    }// sendMultiple()

    /**
     * Wrapper for {@link Uri#getQueryParameter(String)}.
     * 
     * @param uri
     *            see original method.
     * @param param
     *            see original method.
     * @return see original method.
     */
    public static String getQueryParameter(Uri uri, String param) {
        String value = uri.getQueryParameter(param);
        if (TextUtils.isEmpty(value))
            return value;
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH)
            return value
                    .replaceAll(Pattern.quote("+"), Character.toString(' '));
        return value;
    }// getQueryParameter()

    /**
     * Gets boolean query parameter.
     * 
     * @param uri
     *            the URI.
     * @param param
     *            the parameter.
     * @return {@code true} only if the parameter's value is equals to string
     *         value of {@link Boolean#TRUE}, {@code false} otherwise.
     */
    public static boolean getBooleanQueryParameter(Uri uri, String param) {
        return getBooleanQueryParameter(uri, param, false);
    }// getBooleanQueryParameter()

    /**
     * Gets boolean query parameter.
     * 
     * @param uri
     *            the URI.
     * @param param
     *            the parameter.
     * @param defaultValue
     *            the default value if the parameter is not existed, or is
     *            empty.
     * @return {@code true} only if the parameter's value is equals to string
     *         value of {@link Boolean#TRUE}, {@code false} otherwise.
     */
    public static boolean getBooleanQueryParameter(Uri uri, String param,
            boolean defaultValue) {
        String value = uri.getQueryParameter(param);
        if (TextUtils.isEmpty(value))
            return defaultValue;
        return Boolean.TRUE.toString().equals(value);
    }// getBooleanQueryParameter()

    /**
     * Gets display name of a URI by {@link OpenableColumns#DISPLAY_NAME}.
     * 
     * @param context
     *            the context.
     * @param uri
     *            the URI.
     * @return the name, or {@code null} if not available.
     */
    public static String getDisplayName(Context context, Uri uri) {
        Cursor query = context.getContentResolver()
                .query(uri, new String[] { OpenableColumns.DISPLAY_NAME },
                        null, null, null);
        try {
            if (query == null || !query.moveToFirst())
                return null;
            return query.getString(0);
        } finally {
            if (query != null)
                query.close();
        }
    }// getDisplayName()

    /**
     * Gets size of a URI by {@link OpenableColumns#SIZE}.
     * 
     * @param context
     *            the context.
     * @param uri
     *            the URI.
     * @return the size, or {@code -1} if not available.
     */
    public static long getSize(Context context, Uri uri) {
        Cursor query = context.getContentResolver().query(uri,
                new String[] { OpenableColumns.SIZE }, null, null, null);
        try {
            if (query == null || !query.moveToFirst())
                return -1;
            return query.getLong(0);
        } finally {
            if (query != null)
                query.close();
        }
    }// getSize()

    /**
     * Generates new passport which can be used for
     * {@link ShareContract#PARAM_PASSPORT}.
     * 
     * @param context
     *            the context.
     * @return the passport.
     * @see ShareContract#PARAM_PASSPORT
     */
    public static String genPassport(Context context) {
        return SimpleWeakEncryption.encrypt(Sys.getSignatureAsChars(context),
                null, Long.toString(SystemClock.elapsedRealtime()));
    }// genPassport()

    /**
     * Validates a passport.
     * 
     * @param context
     *            the context.
     * @param passport
     *            the passport.
     * @return {@code true} or {@code false}.
     * @see ShareContract#PARAM_PASSPORT
     */
    public static boolean validatePassport(Context context, String passport) {
        if (TextUtils.isEmpty(passport))
            return false;

        long tick = SystemClock.elapsedRealtime();

        try {
            passport = SimpleWeakEncryption.decrypt(
                    Sys.getSignatureAsChars(context), null, passport);
        } catch (Throwable t) {
            return false;
        }

        if (TextUtils.isEmpty(passport))
            return false;
        try {
            tick -= Long.parseLong(passport);
            return tick >= 0 && tick < MAX_DELTA_TIME_FOR_PASSPORT;
        } catch (NumberFormatException e) {
            return false;
        }
    }// validatePassport()

    /**
     * Generates and attaches a passport to a URI.
     * 
     * @param context
     *            the context.
     * @param uri
     *            the URI.
     * @return the new URI carrying a passport.
     */
    public static Uri attachPassport(Context context, Uri uri) {
        return attachPassport(context, uri.buildUpon()).build();
    }// attachPassport()

    /**
     * Generates and attaches a passport to a URI builder.
     * 
     * @param context
     *            the context.
     * @param uriBuilder
     *            the URI builder.
     * @return the input URI builder, for chaining multiple calls into a single
     *         statement.
     */
    public static Uri.Builder attachPassport(Context context,
            Uri.Builder uriBuilder) {
        return uriBuilder.appendQueryParameter(ShareContract.PARAM_PASSPORT,
                genPassport(context));
    }// attachPassport()

}